package com.sc.nft.dao;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.sc.nft.entity.*;
import com.sc.nft.entity.dto.FlowRecordDTO;
import com.sc.nft.entity.dto.NewSecondCollectionGoodsBuyOrderListDTO;
import com.sc.nft.entity.dto.RefundOrderPageDTO;
import com.sc.nft.entity.dto.SecondOrderDTO;
import com.sc.nft.entity.vo.*;
import com.sc.nft.enums.MarketTypeEnum;
import com.sc.nft.enums.OrderStatusEnum;
import com.sc.nft.helper.ElasticsearchHelper;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.core.CountRequest;
import org.elasticsearch.client.core.CountResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.index.query.TermsQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.metrics.ParsedSum;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Repository;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.sc.nft.enums.MarketTypeEnum.COPYRIGHT;
import static com.sc.nft.enums.MarketTypeEnum.FREE_MARKET;
import static com.sc.nft.enums.OrderStatusEnum.*;


@Slf4j
@Repository
@RequiredArgsConstructor
public class SecondOrderEsDao {

    private final ElasticsearchHelper helper;
    private final DigitalCollectionDao digitalCollectionDao;
    private final UserInfoDao userInfoDao;
    private final CatenaInfoDao catenaInfoDao;
    private final ShowLevelDao showLevelDao;

    @Value("${app.metago.es_index.sc_second_order:meta-sc_second_order-alias}")
    private String indexAlias;

    @SneakyThrows
    public void save(SecondOrder doc) {
        try {
            IndexResponse response = helper.index(indexAlias, doc.getId(), doc);
            log.info("写入二级订单 id: {}, version: {}, result: {}", response.getId(), response.getVersion(), response.getResult());
        } catch (IOException e) {
            log.error("ES保存出错 #save(), doc: {}", doc, e);
        }
    }

//    public void update(SecondOrder doc) {
//        try {
//            UpdateResponse response = helper.update(indexAlias, doc.getId(), doc);
//            log.info("更新二级订单 id: {}, version: {}, result: {}", response.getId(), response.getVersion(), response.getResult());
//        } catch (IOException e) {
//            log.error("ES更新出错 #update(), doc: {}", doc, e);
//        }
//    }

    @SneakyThrows
    public SecondOrder findById(long id) {
        return helper.get(indexAlias, String.valueOf(id), SecondOrder.class);
    }

    /**
     * 注：包含逻辑删除的数据
     */
    @SneakyThrows
    public List<SecondOrder> findByIds(List<Long> orderIds) {
        if (CollectionUtils.isEmpty(orderIds)) {
            return new ArrayList<>();
        }
        List<String> ids = orderIds.stream().map(String::valueOf).collect(Collectors.toList());
        return helper.mget(indexAlias, ids, SecondOrder.class);
    }

    /**
     * @see com.sc.nft.mapper.SecondOrderMapper#getMaxPriceByCollectionId(Long)
     */
    @SneakyThrows
    public BigDecimal getMaxPriceByCollectionId(Long collectionId) {
        String today = helper.ofDateTimeFormatter().format(LocalDateTime.of(LocalDate.now(), LocalTime.MIN));
        Aggregations aggregations = helper.aggregation(indexAlias,
                new BoolQueryBuilder()
                        .filter(new TermQueryBuilder("collectionId", collectionId))
                        .filter(new TermQueryBuilder("status", PAYMENT.name()))
                        .filter(new TermQueryBuilder("marketType", FREE_MARKET.name()))
//                        .filter(new TermQueryBuilder("isDelete", false)) // 原实现未应用该条件
                        .filter(new RangeQueryBuilder("createTime").lt(today)),
                AggregationBuilders.max("v").field("buyPrice")
        );

        return helper.parseAggregationToBigDecimalValue(aggregations, "v");
    }

    /**
     * @see com.sc.nft.mapper.SecondOrderMapper#getLastPrice(Long)
     */
    @SneakyThrows
    public BigDecimal getLastPrice(Long collectionId) {
        SearchResponse response = helper.search(new SearchRequest(indexAlias)
                        .source(new SearchSourceBuilder()
                                        .query(new BoolQueryBuilder()
                                                        .filter(new TermQueryBuilder("collectionId", collectionId))
                                                        .filter(new TermQueryBuilder("status", PAYMENT.name()))
//                                .filter(new TermQueryBuilder("isDelete", false)) // 原实现未使用该条件
                                        )
                                        .fetchSource("buyPrice", null)
                                        .sort("id", SortOrder.DESC)
                                        .size(1)
                        )
        );

        return Arrays.stream(response.getHits().getHits())
                .findFirst()
                .map(SearchHit::getSourceAsMap)
                .map(map -> (Double) map.get("buyPrice"))
                .map(BigDecimal::valueOf)
                .orElse(null);
    }

    /**
     * @see com.sc.nft.mapper.SecondOrderMapper#selectNewSecondCollectionGoodsSaleOrderList(Page, NewSecondCollectionGoodsBuyOrderListDTO, Long)
     */
    @SneakyThrows
    public Page<NewSecondCollectionGoodsBuyOrderListVO> selectNewSecondCollectionGoodsSaleOrderList(Page mp,
                                                                                                    NewSecondCollectionGoodsBuyOrderListDTO newSecondCollectionGoodsBuyOrderListDTO, Long userId) {
        BoolQueryBuilder builder = new BoolQueryBuilder()
                .filter(new TermQueryBuilder("salesUserId", userId))
                .filter(new TermQueryBuilder("isDelete", false))
                .filter(new TermQueryBuilder("status", PAYMENT));
        if (StrUtil.isNotBlank(newSecondCollectionGoodsBuyOrderListDTO.getDate())) {
            // 2024-08 | 转换成具体的时间范围 [2024-08-01 00:00:00, 2024-09-01 00:00:00)
            List<String> parts = StrUtil.split(newSecondCollectionGoodsBuyOrderListDTO.getDate(), "-");
            int year = Integer.parseInt(parts.get(0));
            int month = Integer.parseInt(parts.get(1));
            builder.filter(new RangeQueryBuilder("createTime")
                    .gte(helper.ofDateTimeString(LocalDate.of(year, month, 1), LocalTime.MIN))
                    .lt(helper.ofDateTimeString(LocalDate.of(year, month, 1).plusMonths(1), LocalTime.MIN))
            );
        }

        SearchResponse response = helper.search(new SearchRequest(indexAlias).source(
                new SearchSourceBuilder()
                        .query(builder)
                        .sort("id", SortOrder.DESC)
                        .from((int) ((mp.getCurrent() - 1) * mp.getSize()))
                        .size((int) mp.getSize())
        ));

        List<NewSecondCollectionGoodsBuyOrderListVO> records = Arrays.stream(response.getHits().getHits()).map(this::toNewSecondCollectionGoodsBuyOrderListVO).collect(Collectors.toList());
        fillCreationContent(records);
        Page<NewSecondCollectionGoodsBuyOrderListVO> page = new Page<>(mp.getCurrent(), mp.getSize());
        page.setRecords(records);
        page.setTotal(Objects.requireNonNull(response.getHits().getTotalHits()).value);
        return page;
    }

    @SneakyThrows
    private NewSecondCollectionGoodsBuyOrderListVO toNewSecondCollectionGoodsBuyOrderListVO(SearchHit hit) {

        // 由于存在部分字段（如：status）类型与实体类型不一致的情况，使用JSON转换处理比较麻烦，故此直接转为Map后手动赋值
        Map<String, Object> data = hit.getSourceAsMap();
        if (CollectionUtils.isEmpty(data)) {
            return null;
        }

        NewSecondCollectionGoodsBuyOrderListVO vo = new NewSecondCollectionGoodsBuyOrderListVO();
        Optional.ofNullable((Number) data.get("id")).map(Number::toString).ifPresent(vo::setOrderId);
        Optional.ofNullable((String) data.get("status")).map(OrderStatusEnum::valueOf).map(OrderStatusEnum::getCode).ifPresent(vo::setStatus);
        Optional.ofNullable((Number) data.get("collectionId")).map(Number::longValue).ifPresent(vo::setCollectionId);
        Optional.ofNullable((Number) data.get("numbering")).map(Number::intValue).ifPresent(vo::setNumbering);
        Optional.ofNullable((String) data.get("orderNo")).ifPresent(vo::setOrderNo);
        Optional.ofNullable((Number) data.get("buyPrice")).map(Number::doubleValue).map(BigDecimal::valueOf).ifPresent(vo::setBuyPrice);
        Optional.ofNullable((Number) data.get("collectionSumCount")).map(Number::intValue).ifPresent(vo::setCollectionSumCount);
        Optional.ofNullable((String) data.get("createTime")).map(v -> DateUtil.parse(v, helper.ofDateTimeFormatPattern())).ifPresent(vo::setCreateTime);
        Optional.ofNullable((String) data.get("firstTitle")).ifPresent(vo::setFirstTitle);
        Optional.ofNullable((String) data.get("marketType")).map(MarketTypeEnum::valueOf).ifPresent(vo::setMarketType);
        // 组装 SerialNumber 字段
        Optional.of(vo).filter(v -> Objects.nonNull(v.getNumbering()) && Objects.nonNull(v.getCollectionSumCount()))
                .map(v -> String.format("%d/%d", v.getNumbering(), v.getCollectionSumCount()))
                .ifPresent(vo::setSerialNumber);
        return vo;
    }

    /**
     * 字段 CreationContent 是通过 join sc_digital_collection 表实现的，这里改用接口拼装
     */
    private void fillCreationContent(List<NewSecondCollectionGoodsBuyOrderListVO> records) {
        if (CollectionUtils.isEmpty(records)) {
            return;
        }

        List<Long> collectionIds = records.stream().map(NewSecondCollectionGoodsBuyOrderListVO::getCollectionId).distinct().collect(Collectors.toList());
        if (CollectionUtils.isEmpty(collectionIds)) {
            return;
        }

        List<DigitalCollection> items = digitalCollectionDao.getByIds(collectionIds);
        if (CollectionUtils.isEmpty(items)) {
            return;
        }

        Map<Long, DigitalCollection> map = items.stream().collect(Collectors.toMap(DigitalCollection::getId, Function.identity()));
        records.forEach(r -> {
            DigitalCollection dc = map.get(r.getCollectionId());
            if (dc != null) {
                r.setCreationContent(dc.getCreationContent());
            }
        });

    }

    @SneakyThrows
    public Page<NewSecondCollectionGoodsBuyOrderListVO> selectNewSecondCollectionGoodsBuyOrderList(Page mp,
                                                                                                   NewSecondCollectionGoodsBuyOrderListDTO newSecondCollectionGoodsBuyOrderListDTO, Long userId) {
        BoolQueryBuilder builder = new BoolQueryBuilder()
                .filter(new TermQueryBuilder("buyUserId", userId))
                .filter(new TermQueryBuilder("isDelete", false));
        if (StrUtil.isNotBlank(newSecondCollectionGoodsBuyOrderListDTO.getDate())) {
            // 2024-08 | 转换成具体的时间范围 [2024-08-01 00:00:00, 2024-09-01 00:00:00)
            List<String> parts = StrUtil.split(newSecondCollectionGoodsBuyOrderListDTO.getDate(), "-");
            int year = Integer.parseInt(parts.get(0));
            int month = Integer.parseInt(parts.get(1));
            builder.filter(new RangeQueryBuilder("createTime")
                    .gte(helper.ofDateTimeString(LocalDate.of(year, month, 1), LocalTime.MIN))
                    .lt(helper.ofDateTimeString(LocalDate.of(year, month, 1).plusMonths(1), LocalTime.MIN))
            );
        }

        builder.filter(new TermsQueryBuilder("status", PAYMENT.name(), WAIT_PAY.name()));

//        builder.should(new TermQueryBuilder("status", PAYMENT.name()));
//        builder.should(new BoolQueryBuilder()
//                .filter(new TermQueryBuilder("status", WAIT_PAY.name()))
////                .filter(new RangeQueryBuilder("createTime").gt(helper.ofDateTimeString(LocalDate.now(), LocalTime.now().minusMinutes(3L))))
//        );

        SearchResponse response = helper.search(new SearchRequest(indexAlias).source(
                new SearchSourceBuilder()
                        .query(builder)
                        .sort("id", SortOrder.DESC)
                        .from((int) ((mp.getCurrent() - 1) * mp.getSize()))
                        .size((int) mp.getSize())
        ));

        List<NewSecondCollectionGoodsBuyOrderListVO> records = Arrays.stream(response.getHits().getHits()).map(this::toNewSecondCollectionGoodsBuyOrderListVO).collect(Collectors.toList());
        fillCreationContent(records);
        Page<NewSecondCollectionGoodsBuyOrderListVO> page = new Page<>(mp.getCurrent(), mp.getSize());
        page.setRecords(records);
        page.setTotal(Objects.requireNonNull(response.getHits().getTotalHits()).value);
        return page;
    }

    @SneakyThrows
    public Page<NewSecondCollectionGoodsBuyOrderListVO> overdueOrderList(Page mp, Long userId) {
        BoolQueryBuilder builder = new BoolQueryBuilder()
                .filter(new TermQueryBuilder("buyUserId", userId))
                .filter(new TermQueryBuilder("isDelete", false))
                .filter(new TermQueryBuilder("status", OVERDUE));

        SearchResponse response = helper.search(new SearchRequest(indexAlias).source(
                new SearchSourceBuilder()
                        .query(builder)
                        .sort("id", SortOrder.DESC)
                        .from((int) ((mp.getCurrent() - 1) * mp.getSize()))
                        .size((int) mp.getSize())
        ));

        List<NewSecondCollectionGoodsBuyOrderListVO> records = Arrays.stream(response.getHits().getHits()).map(this::toNewSecondCollectionGoodsBuyOrderListVO).collect(Collectors.toList());
        fillCreationContent(records);
        Page<NewSecondCollectionGoodsBuyOrderListVO> page = new Page<>(mp.getCurrent(), mp.getSize());
        page.setRecords(records);
        page.setTotal(Objects.requireNonNull(response.getHits().getTotalHits()).value);
        return page;
    }

    @SneakyThrows
    public BigDecimal getSumInCharge() {
        String today = helper.ofDateTimeFormatter().format(LocalDateTime.of(LocalDate.now(), LocalTime.MIN));
        Aggregations aggregations = helper.aggregation(indexAlias,
                new BoolQueryBuilder()
                        .filter(new TermQueryBuilder("status", PAYMENT.name()))
                        .filter(new TermQueryBuilder("isDelete", false))
                        .filter(new RangeQueryBuilder("createTime").lt(today)),
                AggregationBuilders.sum("v").field("charge")
        );

        return helper.parseAggregationToBigDecimalValue(aggregations, "v");
    }

    @SneakyThrows
    public BigDecimal getSecondSellAmount(MarketTypeEnum marketTypeEnum, List<Long> buyUserIds) {
        String today = helper.ofDateTimeFormatter().format(LocalDateTime.of(LocalDate.now(), LocalTime.MIN));
        Aggregations aggregations = helper.aggregation(indexAlias,
                new BoolQueryBuilder()
                        .filter(new TermQueryBuilder("status", PAYMENT.name()))
                        .filter(new TermQueryBuilder("marketType", marketTypeEnum.name()))
                        .filter(new TermQueryBuilder("isDelete", false))
                        .filter(new RangeQueryBuilder("createTime").lt(today))
                        .mustNot(new TermsQueryBuilder("buyUserId", buyUserIds))
                ,
                AggregationBuilders.sum("v").field("buyPrice")
        );

        return helper.parseAggregationToBigDecimalValue(aggregations, "v");
    }

    @SneakyThrows
    public BigDecimal getSecondSellAmountByMarketAccount(MarketTypeEnum marketTypeEnum, List<Long> buyUserIds) {
        String today = helper.ofDateTimeFormatter().format(LocalDateTime.of(LocalDate.now(), LocalTime.MIN));
        Aggregations aggregations = helper.aggregation(indexAlias,
                new BoolQueryBuilder()
                        .filter(new TermQueryBuilder("status", PAYMENT.name()))
                        .filter(new TermQueryBuilder("marketType", marketTypeEnum.name()))
                        .filter(new TermQueryBuilder("isDelete", false))
                        .filter(new RangeQueryBuilder("createTime").lt(today))
                        .filter(new TermsQueryBuilder("buyUserId", buyUserIds))
                ,
                AggregationBuilders.sum("v").field("buyPrice")
        );

        return helper.parseAggregationToBigDecimalValue(aggregations, "v");
    }

    @SneakyThrows
    public Page<FlowRecordVO> flowRecord(Page<?> mp, FlowRecordDTO flowRecordDTO) {
        BoolQueryBuilder builder = new BoolQueryBuilder()
                .filter(new TermQueryBuilder("collectionId", flowRecordDTO.getCollectionId()))
                .filter(new TermQueryBuilder("isDelete", false))
                .filter(new TermQueryBuilder("status", PAYMENT))
                .filter(new TermsQueryBuilder("marketType", FREE_MARKET, COPYRIGHT));

        SearchResponse response = helper.search(new SearchRequest(indexAlias).source(
                new SearchSourceBuilder()
                        .query(builder)
                        .sort("id", SortOrder.DESC)
                        .from((int) ((mp.getCurrent() - 1) * mp.getSize()))
                        .size((int) mp.getSize())
        ));

        List<FlowRecordVO> records = helper.parseItems(response.getHits(), FlowRecordVO.class);
        records.forEach(fr -> fr.setSerialNumber(String.format("%d/%d", fr.getNumbering(), fr.getCollectionSumCount())));

        Page<FlowRecordVO> page = new Page<>(mp.getCurrent(), mp.getSize());
        page.setRecords(records);
        page.setTotal(Objects.requireNonNull(response.getHits().getTotalHits()).value);
        return page;
    }

    @SneakyThrows
    public List<SecondOrder> getWithSettlePerformance() {
        BoolQueryBuilder builder = new BoolQueryBuilder()
                .filter(new TermQueryBuilder("performanceSettleFlag", false))
                .filter(new TermQueryBuilder("isDelete", false))
                .filter(new TermQueryBuilder("status", PAYMENT));

        return helper.search(new SearchRequest(indexAlias)
                .source(
                        new SearchSourceBuilder()
                                .query(builder)
                                .fetchSource(new String[]{"id", "buyUserId", "charge", "dealTime", "buyPrice", "shareProfitPanelId"}, null)
                                .size(10000)
                                .sort("id", SortOrder.ASC)
                ), SecondOrder.class);
    }

    @SneakyThrows
    public Page<Long> idPage(Page<?> mp, Long buyUserId, Long saleUserId, Long collectionId, Date startTime, Date endTime, Integer marketType) {
        BoolQueryBuilder builder = new BoolQueryBuilder().filter(new TermQueryBuilder("isDelete", false));

        Optional.ofNullable(marketType)
                .filter(mt -> mt > 0)
                .map(MarketTypeEnum::getMarketTypeEnum)
                .map(MarketTypeEnum::name)
                .ifPresent(mt -> builder.filter(new TermQueryBuilder("marketType", mt)));
        Optional.ofNullable(collectionId).ifPresent(userId -> builder.filter(new TermQueryBuilder("collectionId", userId)));
        Optional.ofNullable(buyUserId).ifPresent(userId -> builder.filter(new TermQueryBuilder("buyUserId", userId)));
        Optional.ofNullable(saleUserId).ifPresent(userId -> builder.filter(new TermQueryBuilder("salesUserId", userId)));
        String startTimeString = Optional.ofNullable(startTime).map(dt -> DateUtil.format(dt, helper.ofDateTimeFormatPattern())).orElse(null);
        String endTimeString = Optional.ofNullable(endTime).map(dt -> DateUtil.format(dt, helper.ofDateTimeFormatPattern())).orElse(null);
        RangeQueryBuilder dealTimeBuilder = new RangeQueryBuilder("dealTime");
        if (StrUtil.isNotBlank(startTimeString)) {
            dealTimeBuilder.gt(startTimeString);
        }
        if (StrUtil.isNotBlank(endTimeString)) {
            dealTimeBuilder.lt(endTimeString);
        }
        if (StrUtil.isNotBlank(startTimeString) || StrUtil.isNotBlank(endTimeString)) {
            builder.filter(dealTimeBuilder);
        }

        SearchResponse response = helper.search(new SearchRequest(indexAlias).source(
                new SearchSourceBuilder()
                        .query(builder)
                        .fetchSource("id", null)
                        .sort("id", SortOrder.DESC)
                        .from((int) ((mp.getCurrent() - 1) * mp.getSize()))
                        .size((int) mp.getSize())
        ));

        List<Long> records = helper.parseItems(response.getHits(), hit -> Long.valueOf(hit.getId()));
        Page<Long> page = new Page<>(mp.getCurrent(), mp.getSize());
        page.setRecords(records);
        page.setTotal(Objects.requireNonNull(response.getHits().getTotalHits()).value);
        return page;
    }

    public List<SecondOrderDTO> adminPageByIdList(List<Long> ids) {
        List<SecondOrder> items = findByIds(ids);
        if (CollectionUtils.isEmpty(items)) {
            return new ArrayList<>();
        }

        // 查询藏品信息
        List<Long> collectionIds = items.stream().filter(Objects::nonNull).map(SecondOrder::getCollectionId).distinct().collect(Collectors.toList());
        Map<Long, DigitalCollection> dcs = digitalCollectionDao.getByIds(collectionIds).stream().filter(Objects::nonNull).collect(Collectors.toMap(DigitalCollection::getId, Function.identity(), (a, b) -> a));

        // sc_catena_info
        List<Long> catenaIds = dcs.values().stream().map(DigitalCollection::getCatenaId).distinct().collect(Collectors.toList());
        Map<Long, String> catenaNameMap = catenaInfoDao.listByIds(catenaIds).stream().filter(Objects::nonNull).collect(Collectors.toMap(CatenaInfo::getId, CatenaInfo::getName, (a, b) -> a));

        // sc_show_level
        List<Integer> showLevels = dcs.values().stream().map(DigitalCollection::getShowLevel).distinct().collect(Collectors.toList());
        Map<Long, String> showLevelNameMap = showLevelDao.listByIds(showLevels).stream().filter(Objects::nonNull).collect(Collectors.toMap(ShowLevel::getId, ShowLevel::getShowLevelName, (a, b) -> a));

        // 查询用户信息
        List<Long> userIds = items.stream().filter(Objects::nonNull).flatMap(e -> Stream.of(e.getSalesUserId(), e.getBuyUserId())).distinct().collect(Collectors.toList());
        Map<Long, String> userTelMap = userInfoDao.getByIdList(userIds).stream().collect(Collectors.toMap(UserInfo::getId, UserInfo::getUserTel, (a, b) -> a));

        return items.stream().map(e -> toSecondOrderDTO(e, dcs, catenaNameMap, showLevelNameMap, userTelMap)).collect(Collectors.toList());
    }

    private SecondOrderDTO toSecondOrderDTO(SecondOrder entity, Map<Long, DigitalCollection> dcs, Map<Long, String> catenaNameMap, Map<Long, String> showLevelNameMap, Map<Long, String> userTelMap) {
        DigitalCollection dc = dcs.get(entity.getCollectionId());

        SecondOrderDTO dto = new SecondOrderDTO();
        dto.setId(entity.getId());
        dto.setCollectionId(entity.getCollectionId());
        dto.setOrderNo(entity.getOrderNo());
        if (dc != null) {
            dto.setFirstTitle(dc.getFirstTitle());
            if (dc.getCatenaId() != null) {
                dto.setCatenaId(dc.getCatenaId());
                dto.setCatenaName(catenaNameMap.get(dc.getCatenaId()));
            }
            if (dc.getShowLevel() != null) {
                dto.setShowLevel(dc.getShowLevel().longValue());
                dto.setShowLevelName(showLevelNameMap.get(dc.getShowLevel().longValue()));
            }
        }
        dto.setSaleUserTel(userTelMap.get(entity.getSalesUserId()));
        dto.setBuyUserTel(userTelMap.get(entity.getBuyUserId()));
        dto.setNumbering(String.format("%d/%d", entity.getNumbering(), entity.getCollectionSumCount()));
        dto.setBuyPrice(entity.getBuyPrice());
        dto.setCharge(entity.getCharge());
        dto.setDealTime(entity.getDealTime());
        dto.setRealAmount(entity.getRealAmount());
        dto.setPreventHoard(entity.getPreventHoard());
        dto.setMarketType(entity.getMarketType());
        dto.setStatus(entity.getStatus());
        dto.setGas(entity.getGas());
        dto.setReceiveType(entity.getReceiveType());

        return dto;
    }

    @SneakyThrows
    public SecondOverdueOrderDetailsVO overdueOrderDetails(Long orderId, Long userId) {
        BoolQueryBuilder builder = new BoolQueryBuilder()
                .filter(new TermQueryBuilder("buyUserId", userId))
                .filter(new TermQueryBuilder("isDelete", false))
                .filter(new TermQueryBuilder("id", orderId));

        List<SecondOrder> items = helper.search(new SearchRequest(indexAlias).source(
                new SearchSourceBuilder().query(builder)
        ), SecondOrder.class);

        if (CollectionUtils.isEmpty(items)) {
            return null;
        }

        SecondOrder entity = items.get(0);
        if (entity == null) {
            return null;
        }

        // 组装数据
        SecondOverdueOrderDetailsVO vo = new SecondOverdueOrderDetailsVO();

        vo.setFirstTitle(entity.getFirstTitle());
        vo.setSerialNumber(String.format("%d/%d", entity.getNumbering(), entity.getCollectionSumCount()));
        vo.setBuyPrice(entity.getBuyPrice());
        vo.setOrderNo(entity.getOrderNo());
        vo.setOrderId(entity.getId().toString());
        vo.setStatus(entity.getStatus());
        vo.setCreateTime(DateUtil.format(entity.getCreateTime(), helper.ofDateTimeFormatPattern()));
        vo.setRealCloseTime(entity.getRealCloseTime());

        vo.setSalesUserName("");
        vo.setBuyUserName("");
        vo.setCreationContent("");
        vo.setLevelName("");

        // 填充 NickName
        List<UserInfo> users = userInfoDao.getByIdList(Arrays.asList(entity.getSalesUserId(), entity.getBuyUserId()));
        if (!CollectionUtils.isEmpty(users)) {
            for (UserInfo user : users) {
                if (Objects.equals(user.getId(), entity.getSalesUserId())) {
                    vo.setSalesUserName(user.getNickName());
                } else if (Objects.equals(user.getId(), entity.getBuyUserId())) {
                    vo.setBuyUserName(user.getNickName());
                }
            }
        }

        // 填充 CreationContent
        DigitalCollection dc = digitalCollectionDao.getById(entity.getCollectionId());
        if (dc != null) {
            vo.setCreationContent(dc.getCreationContent());
        }

        // 填充 LevelName
        if (dc != null && dc.getShowLevel() != null) {
            ShowLevel levelName = showLevelDao.getShowNameById(dc.getShowLevel().longValue());
            if (levelName != null) {
                vo.setLevelName(levelName.getShowLevelName());
            }
        }

        return vo;
    }

    @SneakyThrows
    public IPage<RefundOrderPageVO> refundOrderPage(RefundOrderPageDTO request, Long userId) {
        BoolQueryBuilder builder = new BoolQueryBuilder()
                .filter(new TermQueryBuilder("isDelete", false))
                .filter(new TermQueryBuilder("buyUserId", userId));

        if (StrUtil.isNotBlank(request.getOrderNo())) {
            builder.filter(new TermQueryBuilder("orderNo", request.getOrderNo()));
        }

        Optional.ofNullable(request.getStatus()).map(OrderStatusEnum::name)
                .ifPresent(se -> builder.filter(new TermQueryBuilder("status", se)));

        Page<?> mp = request.toMp();

        SearchResponse response = helper.search(new SearchRequest(indexAlias).source(
                new SearchSourceBuilder()
                        .query(builder)
                        .fetchSource(new String[]{"orderNo", "firstTitle", "buyPrice", "status", "createTime", "payTime"}, null)
                        .sort("id", SortOrder.DESC)
                        .from((int) ((mp.getCurrent() - 1) * mp.getSize()))
                        .size((int) mp.getSize())
        ));

        List<RefundOrderPageVO> records = helper.parseItems(response.getHits(), RefundOrderPageVO.class);
        Page<RefundOrderPageVO> page = new Page<>(mp.getCurrent(), mp.getSize());
        page.setRecords(records);
        page.setTotal(Objects.requireNonNull(response.getHits().getTotalHits()).value);
        return page;
    }

    @SneakyThrows
    public Integer countOrder(Long userId) {

        CountResponse response = helper.count(new CountRequest(indexAlias).query(
                new BoolQueryBuilder()
                        .filter(new TermQueryBuilder("status", PAYMENT.name()))
                        .filter(new TermQueryBuilder("isDelete", false))
                        .filter(new TermQueryBuilder("buyUserId", userId))
        ));

        long cnt = response.getCount();
        return Math.min((int) cnt, 5);
    }

    /**
     * 批量写入索引
     */
    public void bulkIndex(List<SecondOrder> entities) throws IOException {
        if (CollectionUtils.isEmpty(entities)) {
            return;
        }

        BulkResponse response = helper.bulkIndex(indexAlias, entities, item -> item.getId().toString());
        log.info("Bulk index status: {}, size: {}", response.status(), response.getItems().length);
    }

    private NewSecondCollectionOrderDetailsVO toNewSecondCollectionOrderDetailsVO(SecondOrder entity) {
        NewSecondCollectionOrderDetailsVO vo = new NewSecondCollectionOrderDetailsVO();

        // 订单字段
        vo.setFirstTitle(entity.getFirstTitle());
        vo.setSerialNumber(String.format("%d/%d", entity.getNumbering(), entity.getCollectionSumCount()));
        vo.setAntiHoardPrice(entity.getPreventHoard());
        vo.setRealAmount(entity.getRealAmount());
        vo.setBuyPrice(entity.getBuyPrice());
        vo.setCharge(entity.getCharge());
        vo.setDealTime(DateUtil.format(entity.getDealTime(), helper.ofDateTimeFormatPattern()));
        vo.setOrderNo(entity.getOrderNo());
        vo.setStatus(entity.getStatus());
        vo.setCreateTime(DateUtil.format(entity.getCreateTime(), helper.ofDateTimeFormatPattern()));
        vo.setGas(entity.getGas());
        vo.setOrderId(entity.getId().toString());
        vo.setReceiveType(entity.getReceiveType());

        // 其它字段
        vo.setCreationContent("");
        vo.setLevelName("");
        vo.setSalesUserName("");
        vo.setBuyUserName("");

        // 填充 NickName
        List<UserInfo> users = userInfoDao.getByIdList(Arrays.asList(entity.getSalesUserId(), entity.getBuyUserId()));
        if (!CollectionUtils.isEmpty(users)) {
            for (UserInfo user : users) {
                if (Objects.equals(user.getId(), entity.getSalesUserId())) {
                    vo.setSalesUserName(user.getNickName());
                } else if (Objects.equals(user.getId(), entity.getBuyUserId())) {
                    vo.setBuyUserName(user.getNickName());
                }
            }
        }

        // 填充 CreationContent
        DigitalCollection dc = digitalCollectionDao.getById(entity.getCollectionId());
        if (dc != null) {
            vo.setCreationContent(dc.getCreationContent());
        }

        // 填充 LevelName
        if (dc != null && dc.getShowLevel() != null) {
            ShowLevel levelName = showLevelDao.getShowNameById(dc.getShowLevel().longValue());
            if (levelName != null) {
                vo.setLevelName(levelName.getShowLevelName());
            }
        }

        return vo;
    }

    public NewSecondCollectionOrderDetailsVO nweSecondCollectionOrderDetails(Long orderId, Long userId) {
        SecondOrder entity = findById(orderId);

        // 对应查询条件 sso.id = ? AND sso.buy_user_id = ? AND sso.is_delete = 0
        if (entity == null || BooleanUtil.isTrue(entity.getIsDelete()) || !Objects.equals(userId, entity.getBuyUserId())) {
            return null;
        }

        // 组装字段
        return toNewSecondCollectionOrderDetailsVO(entity);
    }

    public NewSecondCollectionOrderDetailsVO nweSecondCollectionOrderSaleDetails(Long orderId, Long userId) {
        SecondOrder entity = findById(orderId);

        // 对应查询条件 sso.id = ? AND sso.sales_user_id = ? AND sso.is_delete = 0
        if (entity == null || BooleanUtil.isTrue(entity.getIsDelete()) || !Objects.equals(userId, entity.getSalesUserId())) {
            return null;
        }

        // 组装字段
        return toNewSecondCollectionOrderDetailsVO(entity);
    }

    @SneakyThrows
    public List<SecondOrderExportListVO> getListByCollectionIdAndTime(List<Long> collectionIdList, Integer marketType, Date startTime, Date endTime) {
        BoolQueryBuilder builder = new BoolQueryBuilder()
                .filter(new TermQueryBuilder("isDelete", false))
                .filter(new TermQueryBuilder("status", PAYMENT.name()));

        Optional.ofNullable(marketType)
                .filter(mt -> mt > 0)
                .map(MarketTypeEnum::getMarketTypeEnum)
                .map(MarketTypeEnum::name)
                .ifPresent(mt -> builder.filter(new TermQueryBuilder("marketType", mt)));
        if (!CollectionUtils.isEmpty(collectionIdList)) {
            builder.filter(new TermsQueryBuilder("collectionId", collectionIdList.stream().map(Object::toString).collect(Collectors.toList())));
        }
        Optional.ofNullable(collectionIdList)
                .ifPresent(userId -> builder.filter(new TermsQueryBuilder("collectionId", userId)));
        String startTimeString = Optional.ofNullable(startTime).map(dt -> DateUtil.format(dt, helper.ofDateTimeFormatPattern())).orElse(null);
        String endTimeString = Optional.ofNullable(endTime).map(dt -> DateUtil.format(dt, helper.ofDateTimeFormatPattern())).orElse(null);
        RangeQueryBuilder dealTimeBuilder = new RangeQueryBuilder("dealTime");
        if (StrUtil.isNotBlank(startTimeString)) {
            dealTimeBuilder.gt(startTimeString);
        }
        if (StrUtil.isNotBlank(endTimeString)) {
            dealTimeBuilder.lt(endTimeString);
        }
        if (StrUtil.isNotBlank(startTimeString) || StrUtil.isNotBlank(endTimeString)) {
            builder.filter(dealTimeBuilder);
        }

        List<SecondOrder> list = helper.search(new SearchRequest(indexAlias).source(
                new SearchSourceBuilder()
                        .query(builder)
                        .sort("id", SortOrder.DESC)
        ), SecondOrder.class);

        if (CollectionUtils.isEmpty(list)) {
            return new ArrayList<>();
        }

        // 查询用户信息
        List<Long> userIds = list.stream().filter(Objects::nonNull).flatMap(e -> Stream.of(e.getSalesUserId(), e.getBuyUserId())).distinct().collect(Collectors.toList());
        Map<Long, String> userTelMap = userInfoDao.getByIdList(userIds).stream().collect(Collectors.toMap(UserInfo::getId, UserInfo::getUserTel, (a, b) -> a));

        return list.stream().filter(Objects::nonNull).map(e -> toSecondOrderExportListVO(e, userTelMap)).collect(Collectors.toList());
    }

    private SecondOrderExportListVO toSecondOrderExportListVO(SecondOrder entity, Map<Long, String> userTelMap) {
        SecondOrderExportListVO vo = new SecondOrderExportListVO();
        vo.setOrderNo(entity.getOrderNo());
        vo.setBuyPrice(entity.getBuyPrice());
        vo.setCharge(entity.getCharge());
        vo.setNumbering(String.format("%d/%d", entity.getNumbering(), entity.getCollectionSumCount()));
        vo.setCollectionId(entity.getCollectionId());
        vo.setDealTime(entity.getDealTime());
        vo.setFirstTitle(entity.getFirstTitle());
        vo.setMarketType(entity.getMarketType().getCode());
        vo.setMarketTypeName(entity.getMarketType().getDescription());
        vo.setPreventHoard(entity.getPreventHoard());
        vo.setRealAmount(entity.getRealAmount());
        vo.setSaleUserTel(MapUtil.getStr(userTelMap, entity.getSalesUserId(), ""));
        vo.setBuyUserTel(MapUtil.getStr(userTelMap, entity.getBuyUserId(), ""));
        return vo;
    }

    @SneakyThrows
    public Map<Long, Long> groupSalesCount(List<Long> collectionIds) {
        Aggregations aggregations = helper.aggregation(indexAlias,
                new BoolQueryBuilder()
                        .filter(new TermsQueryBuilder("collectionId", collectionIds))
                        .filter(new TermQueryBuilder("isDelete", false))
                        .filter(new TermQueryBuilder("status", PAYMENT)),
                AggregationBuilders.terms("buckets").field("collectionId").size(1000));

        List<? extends Terms.Bucket> buckets = ((ParsedLongTerms) aggregations.get("buckets")).getBuckets();
        if (CollectionUtils.isEmpty(buckets)) {
            return new HashMap<>();
        }

        return buckets.stream().collect(Collectors.toMap(b -> b.getKeyAsNumber().longValue(), MultiBucketsAggregation.Bucket::getDocCount));
    }

    @SneakyThrows
    public Map<Long, BigDecimal> groupPreventHoardIncome(List<Long> collectionIds) {
        Aggregations aggregations = helper.aggregation(indexAlias,
                new BoolQueryBuilder()
                        .filter(new TermsQueryBuilder("collectionId", collectionIds)),
                AggregationBuilders.terms("buckets").field("collectionId").size(1000)
                        .subAggregation(AggregationBuilders.sum("sumPreventHoard").field("preventHoard"))
        );

        List<? extends Terms.Bucket> buckets = ((ParsedLongTerms) aggregations.get("buckets")).getBuckets();
        if (CollectionUtils.isEmpty(buckets)) {
            return new HashMap<>();
        }

        final Map<Long, BigDecimal> data = new HashMap<>();
        for (Terms.Bucket bucket : buckets) {
            long collectionId = bucket.getKeyAsNumber().longValue();
            Aggregation a = bucket.getAggregations().get("sumPreventHoard");
            BigDecimal value = new BigDecimal(((ParsedSum) a).getValueAsString());
            data.put(collectionId, value);
        }

        return data;
    }

    @SneakyThrows
    public Page<FlowRecordVO> flowRecordWithConsensus(Page mp, FlowRecordDTO flowRecordDTO, boolean isConsensus) {
        BoolQueryBuilder builder = new BoolQueryBuilder()
                .filter(new TermQueryBuilder("collectionId", flowRecordDTO.getCollectionId()))
                .filter(new TermQueryBuilder("isDelete", false))
                .filter(new TermQueryBuilder("status", PAYMENT))
                .filter(new TermsQueryBuilder("marketType", FREE_MARKET, COPYRIGHT))
                .filter(new TermQueryBuilder("isConsensus", isConsensus));

        SearchResponse response = helper.search(new SearchRequest(indexAlias).source(
                new SearchSourceBuilder()
                        .query(builder)
                        .sort("id", SortOrder.DESC)
                        .from((int) ((mp.getCurrent() - 1) * mp.getSize()))
                        .size((int) mp.getSize())
        ));

        List<FlowRecordVO> records = helper.parseItems(response.getHits(), FlowRecordVO.class);
        records.forEach(fr -> fr.setSerialNumber(String.format("%d/%d", fr.getNumbering(), fr.getCollectionSumCount())));

        Page<FlowRecordVO> page = new Page<>(mp.getCurrent(), mp.getSize());
        page.setRecords(records);
        page.setTotal(Objects.requireNonNull(response.getHits().getTotalHits()).value);
        return page;
    }
}
